using WCell.Constants;
using WCell.RealmServer.Entities;
using WCell.RealmServer.Global;
using WCell.RealmServer.Handlers;
using WCell.Util;
using WCell.Util.Graphics;

namespace WCell.RealmServer.Battlegrounds
{
	/// <summary>
	/// Contains all the neccessary battleground information about a player.
	/// </summary>
	public class BattlegroundInfo
	{
		#region Fields
		private Character _chr;
		private BattlegroundRelation[] _relations;
		private BattlegroundInvitation _invitation;
		private int _relationCount;

		private Map m_EntryMap;
		private Vector3 _entryPosition;
		private float _entryOrientation;

		private bool _isDeserter;

		private BattlegroundTeam _team;
		private BattlegroundStats _stats;
		#endregion

		public BattlegroundInfo(Character chr)
		{
			_chr = chr;
			_relations = new BattlegroundRelation[BattlegroundMgr.MaxQueuesPerChar];
		}

		#region Properties
		/// <summary>
		/// The character this information is associated with.
		/// </summary>
		public Character Character
		{
			get { return _chr; }
			internal set { _chr = value; }
		}

		/// <summary>
		/// The battlegrounds team this character is associated with.
		/// </summary>
		public BattlegroundTeam Team
		{
			get { return _team; }
			internal set
			{
				_team = value;
				_chr.Record.BattlegroundTeam = value == null ? BattlegroundSide.End : value.Side;
			}
		}

		/// <summary>
		/// Stats of current or last Battleground (or null)
		/// </summary>
		public BattlegroundStats Stats
		{
			get { return _stats; }
			internal set { _stats = value; }
		}

		/// <summary>
		/// Holds the outstanding, if any, invitation to a battleground team.
		/// </summary>
		public BattlegroundInvitation Invitation
		{
			get { return _invitation; }
			internal set
			{
				if (value != _invitation)
				{
					if (value == null)
					{
						_chr.RemoveUpdateAction(_invitation.CancelTimer);
					}

					_invitation = value;
				}
			}
		}

		/// <summary>
		/// The map that this character was originally in before going to the battlegrounds.
		/// </summary>
		public Map EntryMap
		{
			get { return m_EntryMap; }
			set { m_EntryMap = value; }
		}

		/// <summary>
		/// The position that this character was originally at before going to the battlegrounds.
		/// </summary>
		public Vector3 EntryPosition
		{
			get { return _entryPosition; }
			set { _entryPosition = value; }
		}

		/// <summary>
		/// The orientation that this character was originally in before going to the battlegrounds.
		/// </summary>
		public float EntryOrientation
		{
			get { return _entryOrientation; }
			set { _entryOrientation = value; }
		}

		/// <summary>
		/// Whether or not this character is considered a deserter. (Deserters cannot join battlegrounds)
		/// </summary>
		public bool IsDeserter
		{
			get { return _isDeserter; }
			set
			{
				_isDeserter = value;

				if (_isDeserter)
				{
					CancelAllRelations();
				}
			}
		}

		/// <summary>
		/// Whether or not this character is enqueued for any battlegrounds.
		/// </summary>
		public bool IsEnqueuedForBattleground
		{
			get
			{
				return _relationCount > 0;
			}
		}

		/// <summary>
		/// The battlegrounds relations for this character, if any.
		/// </summary>
		public BattlegroundRelation[] Relations
		{
			get { return _relations; }
		}

		/// <summary>
		/// The number of current battlegrounds relations.
		/// </summary>
		public int RelationCount
		{
			get { return _relationCount; }
		}

		/// <summary>
		/// Whether or not this character can queue for any more battlegrounds.
		/// </summary>
		public bool HasAvailableQueueSlots
		{
			get { return _relationCount < BattlegroundMgr.MaxQueuesPerChar; }
		}
		#endregion

		/// <summary>
		/// Returns the character to their original location prior to entering the Battleground.
		/// </summary>
		public void TeleportBack()
		{
			if (m_EntryMap == null || m_EntryMap.IsDisposed || _entryPosition.X == 0)
			{
				_chr.TeleportToBindLocation();
			}
			else
			{
				_chr.TeleportTo(m_EntryMap, ref _entryPosition, _entryOrientation);
			}

			m_EntryMap = null;
		}

		/// <summary>
		/// Sets the entry position of the character.
		/// </summary>
		public void SetCharacterEntry(Map map, ref Vector3 pos, float orientation)
		{
			m_EntryMap = map;
			_entryPosition = pos;
			_entryOrientation = orientation;
		}

		/// <summary>
		/// Gets the <see cref="BattlegroundRelation"/> for the given Battleground for
		/// the Character.
		/// </summary>
		/// <param name="bgId"></param>
		/// <returns></returns>
		public BattlegroundRelation GetRelation(BattlegroundId bgId)
		{
			for (var i = 0; i < _relations.Length; i++)
			{
				var request = _relations[i];

				if (request != null && request.Queue.ParentQueue.Template.Id == bgId)
				{
					return request;
				}
			}

			return null;
		}

		public bool IsEnqueuedFor(BattlegroundId bgId)
		{
			var relation = GetRelation(bgId);

			return relation != null && relation.IsEnqueued;
		}

		public bool CancelIfEnqueued(BattlegroundId bgId)
		{
			_chr.ContextHandler.EnsureContext();

			for (var i = 0; i < _relations.Length; i++)
			{
				var request = _relations[i];
				if (request != null && request.Queue.ParentQueue.Template.Id == bgId)
				{
					if (request.IsEnqueued)
					{
						request.Cancel();
						return true;
					}
					return false;
				}
			}

			return false;
		}

		public void Cancel(BattlegroundInvitation invite)
		{
			RemoveRelation(invite.QueueIndex);
		}

		public int RemoveRelation(BattlegroundRelation relation)
		{
			return RemoveRelation(relation.BattlegroundId);
		}

		/// <summary>
		/// Removes the <see cref="BattlegroundRelation"/> for the given Battleground.
		/// This also cancels invitations and leaves the Battleground.
		/// If it was a Queue request for the Group and this is the GroupLeader, it also
		/// removes everyone else from the Queue.
		/// </summary>
		/// <param name="id"></param>
		/// <returns>The index of the removed relation or -1 if none removed</returns>
		public int RemoveRelation(BattlegroundId id)
		{
			_chr.EnsureContext();

			for (var i = 0; i < _relations.Length; i++)
			{
				var relation = _relations[i];
				if (relation != null && relation.Queue.ParentQueue.Template.Id == id)
				{
					RemoveRelation(i, relation, true);
					return i;
				}
			}
			return -1;
		}

		/// <summary>
		/// Removes the corresponding relation and removes it from the queue
		/// if it is enqueued.
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public int CancelRelation(BattlegroundId id)
		{
			_chr.EnsureContext();

			for (var i = 0; i < _relations.Length; i++)
			{	
				var relation = _relations[i];
				if (relation != null && relation.Queue.ParentQueue.Template.Id == id)
				{
					CancelRelation(i, relation, true);
					return i;
				}
			}
			return -1;
		}

		public int CancelRelation(int index, BattlegroundRelation relation, bool charActive)
		{
			_chr.EnsureContext();

			var queue = relation.Queue;
			if (queue != null)
			{
				queue.Remove(relation);
			}

			RemoveRelation(index, relation, charActive);

			return index;
		}

		/// <summary>
		/// Make sure the given index is valid
		/// </summary>
		/// <param name="index"></param>
		public void RemoveRelation(int index)
		{
			RemoveRelation(index, _relations[index], true);
		}

		internal void RemoveRelation(int index, BattlegroundRelation relation, bool isCharActive)
		{
			_chr.EnsureContext();
			_relations[index] = null;

			var invite = Invitation;
			if (invite != null)
			{
				// cancel reserved slot
				invite.Team.ReservedSlots--;
				Invitation = null;
			}

			var bgId = relation.BattlegroundId;
			var bg = _chr.Map as Battleground;
			if (bg != null &&
				bg.Template.Id == bgId &&
				!relation.IsEnqueued &&
				!_chr.IsTeleporting &&
				isCharActive)
			{
				bg.TeleportOutside(_chr);
			}

			if (isCharActive)
			{
				BattlegroundHandler.ClearStatus(_chr, index);
			}

			if (relation.IsEnqueued && relation.Characters.CharacterCount > 1)
			{
				var group = _chr.Group;
				if (group != null && group.IsLeader(_chr))
				{
					// also dequeue everyone else
					relation.Characters.ForeachCharacter(chr =>
					{
						if (chr != _chr)
						{
							chr.ExecuteInContext(() =>
							{
								chr.Battlegrounds.RemoveRelation(bgId);
							});
						}
					});
				}
			}
		}

		/// <summary>
		/// Invites this Character to the given Battleground or enqueues him/her.
		/// </summary>
		internal void InviteTo(BattlegroundTeam team)
		{
			var index = GetIndex(team.Battleground.Template.Id);
			BattlegroundRelation relation;
			if (index == -1)
			{
				index = AddRelation(relation = new BattlegroundRelation(team.Queue, _chr, false));
			}
			else
			{
				relation = _relations[index];
			}
			InviteTo(team, index, relation);
		}

		internal void InviteTo(BattlegroundTeam team, int queueIndex, BattlegroundRelation relation)
		{
			_chr.EnsureContext();

			relation.IsEnqueued = false;
			var bg = team.Battleground;
			var invite = new BattlegroundInvitation(team, queueIndex);
			_chr.Battlegrounds.Invitation = invite;

			invite.CancelTimer = _chr.CallDelayed(BattlegroundMgr.InvitationTimeoutMillis, obj =>
			{
				RemoveRelation(bg.Template.Id);
			});

			BattlegroundHandler.SendStatusInvited(_chr);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="relation"></param>
		/// <returns>The index of the newly created relation</returns>
		public int AddRelation(BattlegroundRelation relation)
		{
			_chr.EnsureContext();

			var index = (int)ArrayUtil.AddOnlyOne(ref _relations, relation);

			_relationCount++;
			var queue = relation.Queue.ParentQueue;
			if (queue != null)
			{
				BattlegroundHandler.SendStatusEnqueued(_chr, index, relation, queue);
			}

			return index;
		}

		public int GetIndex(BattlegroundRelation request)
		{
			var queue = request.Queue.ParentQueue;
			if (queue != null)
			{
				return GetIndex(queue.Template.Id);
			}

			return 0;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		/// <remarks>Requires Context</remarks>
		public int GetIndex(BattlegroundId id)
		{
			_chr.ContextHandler.EnsureContext();

			for (var i = 0; i < _relations.Length; i++)
			{
				var request = _relations[i];
				if (request != null && request.Queue.ParentQueue.Template.Id == id)
				{
					return i;
				}
			}

			return -1;
		}

		/// <summary>
		/// Cancel all relations
		/// </summary>
		public void CancelAllRelations()
		{
			for (var i = 0; i < _relations.Length; i++)
			{
				var relation = _relations[i];
				if (relation != null)
				{
					CancelRelation(i, relation, false);
				}
			}
		}

		internal void OnLogout()
		{
			CancelAllRelations();
		}

		public bool IsParticipating(BattlegroundId bgId)
		{
			if (Team != null)
			{
				return Team.Battleground.Template.Id == bgId;
			}
			return false;
		}
	}
}